package module

import (
	"context"
	"fmt"
	"github.com/sirupsen/logrus"
	etcdv3 "go.etcd.io/etcd/client/v3"
	"sync"
)

type Server interface {
	Project() string
	Address() string
	ModuleType() string
	ModuleName() string
	Replica() uint64
	SearchTypes() []string
}

type Manager interface {
	watch()
	start()
	Registry(srv Server)
	Unregister(moduleType string, moduleName string, moduleReplica uint64)
	Address(moduleType string, moduleName string) string
	Filter(moduleType string, resultType, searchType string) map[string]string // return map[address]module_name
}

type manager struct {
	sync.Mutex
	ctx     context.Context
	project string
	client  *etcdv3.Client
	modules map[string]map[string]map[uint64]string // module_type, module_name, module_replica: address
	servers map[string]Server                       // search_type, result_type
}

func NewManager(client *etcdv3.Client, ctx context.Context, project string) Manager {

	m := &manager{
		ctx:     ctx,
		project: project,
		client:  client,
		modules: make(map[string]map[string]map[uint64]string),
		servers: make(map[string]Server),
	}

	// init modules already in etcd
	m.start()

	// watch etcd
	m.watch()

	return m
}

func (m *manager) Registry(srv Server) {
	var (
		mtype   = srv.ModuleType()
		name    = srv.ModuleName()
		replica = srv.Replica()
		address = srv.Address()
	)

	m.Lock()
	defer m.Unlock()

	if _, mok := m.modules[mtype]; !mok {
		m.modules[mtype] = make(map[string]map[uint64]string)
	}

	if _, nok := m.modules[mtype][name]; !nok {
		m.modules[mtype][name] = make(map[uint64]string)
	}

	m.modules[mtype][name][replica] = address
	m.servers[fmt.Sprintf("%s:%s:%d", mtype, name, replica)] = srv

	logrus.Infof("[module regsitry] [%s:%s:%s:%d] [search_types: %v]", srv.Project(), srv.ModuleType(), srv.ModuleName(), srv.Replica(), srv.SearchTypes())
}

func (m *manager) Unregister(mtype string, name string, replica uint64) {
	var (
		rm_mt, rm_mn bool
	)

	m.Lock()
	defer m.Unlock()

	if _, mok := m.modules[mtype]; mok {
		if _, nok := m.modules[mtype][name]; nok {
			delete(m.modules[mtype][name], replica)
			if len(m.modules[mtype][name]) == 0 {
				delete(m.modules[mtype], name)
				rm_mn = true
			}

			if len(m.modules[mtype]) == 0 {
				delete(m.modules, mtype)
				rm_mt = true
			}
		}
	}

	delete(m.servers, fmt.Sprintf("%s:%s:%d", mtype, name, replica))

	logrus.Warnf("[module unregister] [%s:%s:%s:%d] [rm_mt: %v] [rm_mn: %v]", m.project, mtype, name, replica, rm_mt, rm_mn)
}

func (m *manager) Address(mtype string, name string) (address string) {
	if _, mok := m.modules[mtype]; mok {
		if _, nok := m.modules[mtype][name]; nok {
			for key := range m.modules[mtype][name] {
				address = m.modules[mtype][name][key]
				break
			}
		}
	}

	return
}

func (m *manager) Filter(moduleType, resultType, searchType string) map[string]string {
	var (
		data   = make([]Server, 0)
		result = make(map[string]string)
	)

	for _, srv := range m.servers {
		if srv.ModuleType() == moduleType {
			for _, stype := range srv.SearchTypes() {
				if stype == fmt.Sprintf("%s-%s", resultType, searchType) {
					data = append(data, srv)
				}
			}
		}
	}

	for idx := range data {
		result[data[idx].ModuleName()] = data[idx].Address()
	}

	return result
}
